﻿#region Using namespaces

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using BOL.Utils.Optimization.EvolutionaryAlgorithms;

using QuickGraph;
using QuickGraph.Algorithms;
using QuickGraph.Algorithms.ConnectedComponents;
using QuickGraph.Algorithms.Search;

#endregion

namespace BOL.Apps.GeneNetworkBrowser.Analysis.SubNetwork
{
    public sealed class SubNetworkGenome : SequenceGenomeBase<bool, Network>
    {
        #region IGenome<G, P> implementation

        private Network _network;
        private Gene[] _genes;
        private Relation[] _relations;
        private double[,] _confidence;
        private int _minNodes;
        private int _maxNodes;

        public SubNetworkGenome(Random r, bool[] sequence, Network network, double[,] confidence, int minNodes, int maxNodes)
            : base(r, sequence)
        {
            this._network = network;
            this._genes = network.Vertices.ToArray();
            this._relations = network.Edges.ToArray();
            this._confidence = confidence;
            this._minNodes = minNodes;
            this._maxNodes = maxNodes;
        }

        public override IGenome<bool, Network> Clone()
        {
            var clonedSequence = CloneSequence();
            return new SubNetworkGenome(_r, clonedSequence, _network, _confidence, _minNodes, _maxNodes);
        }

        public override void Evaluate()
        {
            //onIndex; 

            //Fitness = Sequence.Select((v, i) => new { Value = v, Index = i }).Where(x => x.Value).Average(x => Math.Abs(_scores[x.Index]));
        }

        public override IGenome<bool, Network> Offspring()
        {
            return Offspring(_genes[_r.Next(Length)]);
        }

        public SubNetworkGenome Offspring(Gene origin)
        {
            var sequence = new bool[Length];
            int numberOfNodes = _r.Next(_minNodes, _maxNodes);

            foreach (var g in _genes)
                g.NeverVisit();

            Queue<Gene> queue = new Queue<Gene>();
            queue.Enqueue(origin);
            origin.Visit();
            sequence[Array.IndexOf(_genes, origin)] = true;
            IEnumerable<Gene> neighbours;
            int i = 1;

            while (queue.Count != 0 && i < numberOfNodes)
            {
                origin = queue.Dequeue();

                neighbours = _network.OutEdges(origin).Select(e => e.Target).Concat(_network.InEdges(origin).Select(e => e.Source)).OrderBy(g => _r.Next());

                foreach (var neighbour in neighbours)
                {
                    if (!neighbour.IsVisited)
                    {
                        queue.Enqueue(neighbour);
                        neighbour.Visit();
                        sequence[Array.IndexOf(_genes, neighbour)] = true;

                        if (++i == numberOfNodes)
                            break;
                    }
                }
            }

            return new SubNetworkGenome(_r, sequence, _network, _confidence, _minNodes, _maxNodes);
        }

        private static int GetNumberOfConnectedComponents(Network network, Relation common, ref Dictionary<Gene, int> components)
        {
            network.RemoveEdge(common);
            int numberOfConnectedComponents = network.WeaklyConnectedComponents<Gene, Relation>(components);
            network.AddEdge(common);

            return numberOfConnectedComponents;
        }

        public override void Crossover(IGenome<bool, Network> other)
        {
            Crossover(other as SubNetworkGenome);
        }

        public void Crossover(SubNetworkGenome other)
        {
            // gets common relations
            var commonGenes = new List<Gene>();
            for (int i = 0; i < Length; i++)
                if (Sequence[i] & other.Sequence[i])
                    commonGenes.Add(_genes[i]);

            var commonEdges = _network.Edges.Where(e => commonGenes.Contains(e.Source) && commonGenes.Contains(e.Target));

            if (commonEdges.Count() == 0)
                return;

            // find if they are eligible for crossoverable op. onn both copies
            var dad = this.Decode();
            var mom = other.Decode();

            foreach (var commonEdge in commonEdges)
            {
                var dadComponents = new Dictionary<Gene, int>();
                var momComponents = new Dictionary<Gene, int>();

                if (GetNumberOfConnectedComponents(dad, commonEdge, ref dadComponents) > 1 &&
                    GetNumberOfConnectedComponents(mom, commonEdge, ref momComponents) > 1 &&
                    dadComponents.Count(x => x.Value == 1) > 1 &&
                    momComponents.Count(x => x.Value == 1) > 1)
                {
                    int componentKey;

                    // initializes sequences
                    var dadSequence = new bool[Length];
                    var momSequence = new bool[Length];

                    foreach (var component in dadComponents)
                    {
                        componentKey = Array.IndexOf(_genes, component.Key);

                        if (component.Value == 0) // for the first components
                            dadSequence[componentKey] = Sequence[componentKey];
                        else // for the second components
                            momSequence[componentKey] = Sequence[componentKey];
                    }

                    foreach (var component in momComponents)
                    {
                        componentKey = Array.IndexOf(_genes, component.Key);

                        if (component.Value == 0) // for the first components
                            momSequence[componentKey] = other.Sequence[componentKey];
                        else // for the second components
                            dadSequence[componentKey] = other.Sequence[componentKey];
                    }

                    this.Sequence = momSequence;
                    other.Sequence = dadSequence;

                    return;
                }
            }
        }

        public override void Mutate(double mutationRate)
        {
            int numberOfGenesToBeMutated = (int)Math.Round(Length * mutationRate);

            for (int i = 0; i < numberOfGenesToBeMutated; i++)
                Mutate();
        }

        private void Mutate()
        {
            var numberOfNodes = Sequence.Count(x => x);
            
            if (numberOfNodes >= _maxNodes)
                DestructiveMutate();
            else if (numberOfNodes < _minNodes)
                ConstructiveMutate();
            else
            {
                var rand = _r.NextDouble();

                if (rand <= 0.25)
                    DestructiveMutate();
                else if (rand > 0.25 && rand <= 0.5)
                    ConstructiveMutate();
                else
                    SwapMutate();
            }
        }

        private void DestructiveMutate()
        {
            var on = Sequence.Select((v, i) => new { Value = v, Index = i }).Where(x => x.Value).Select(x => x.Index);

            foreach (var i in on)
            {
                if (_connTable[i].Count == 1)
                {
                    Sequence[i] = false;
                    return;
                }
            }
        }

        private void ConstructiveMutate()
        {
            var on = Sequence.Select((v, i) => new { Value = v, Index = i }).Where(x => x.Value).Select(x => x.Index).OrderBy(x => _r.Next());

            foreach (var i in on)
                foreach (var c in _connTable[i])
                    if (!Sequence[c])
                    {
                        Sequence[c] = true;
                        return;
                    }
        }

        //private void ConstructiveMutate()
        //{
        //    // shuffles relations
        //    var shuffledRelations = _relations.OrderBy(e => _r.Next());
        //    int source, target;

        //    foreach (var shuffledRelation in shuffledRelations)
        //    {
        //        source = Array.IndexOf(_genes, shuffledRelation.Source);
        //        target = Array.IndexOf(_genes, shuffledRelation.Target);

        //        if (_sequence[source] && !_sequence[target])
        //        {
        //            _sequence[target] = true;
        //            break;
        //        }
        //        else if (!_sequence[source] && _sequence[target])
        //        {
        //            _sequence[source] = true;
        //            break;
        //        }
        //    }
        //}

        //private void DestructiveMutate()
        //{
        //    // gets one random destructible position
        //    var a = _sequence.Select((v, i) => new { Value = v, Index = i })
        //        .Where(x => x.Value)
        //        .Select(x => x.Index)
        //        .Where(x => _network.Degree(_genes[x]) == 1)
        //        .OrderBy(x => _r.Next());

        //    if (a.Any())
        //    {
        //        var destructible = a.First();
        //        _sequence[destructible] = false;
        //    }
        //}

        private void SwapMutate()
        {
            DestructiveMutate();
            ConstructiveMutate();
        }
           
        public override Network Decode()
        {
            var genes = Sequence.Select((v, i) => new { Value = v, Index = i }).Where(x => x.Value).Select(x => _genes[x.Index]);
            var relations = _network.Edges.Where(x => genes.Contains(x.Source) && genes.Contains(x.Target));

            var network = new Network(Sequence.GetHashCode().ToString());
            network.AddVertexRange(genes);
            network.AddEdgeRange(relations);

            return network;
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            unchecked
            {
                const int p = 16777619;
                int hash = (int)2166136261;

                for (int i = 0; i < Length; i++)
                    hash = (hash ^ Sequence[i].GetHashCode()) * p;

                hash += hash << 13;
                hash ^= hash >> 7;
                hash += hash << 3;
                hash ^= hash >> 17;
                hash += hash << 5;

                return hash;
            }

            //int hash = 17;

            //foreach (var n in _sequence)
            //    hash = hash * 23 + n.GetHashCode();

            //return hash;

        }

        public override string ToString()
        {
            return String.Concat(Sequence.Select(x => (x) ? 1 : 0));
        }

        #endregion
    }
}
